跳到主要内容

Kubernetes Pod 相关

不可变基础设施是啥?​

在以往传统的开发运维体系中,软件开发完成后,需要工程师或管理员通过SSH 连接到他们的服务器上,然后进行一些脚本安装、deb/rpm 包的安装工作,并逐个机器地调整对应的配置参数及文件。后续还会根据需要对该环境进行不断更改,比如 kernel 升级、配置更新、打补丁等。

随着这种类似变更的操作越来越多,没有人能弄清楚这个环境具体经历了哪些操作,而后续的变更也经常会遇到各种意想不到的诡异事情,比如软件包的循环依赖、参数的配置不一致、版本漂移等问题。

基础设施会变得越来越脆弱、敏感,一些小的改动都有可能引发大的不可预知的结果,这令广大开发者和环境管理员异常抓狂,他们需要凭借自己丰富的技术积累,耗费大量的时间去排查解决。

通常来说,这种可变基础设施会导致以下问题:

  • 持续的变更修改给服务运行态引入过多的中间态,增加了不可预知的风险;
  • 故障发生时,难以及时快速构建出新的服务副本;
  • 不易标准化,交付运维过程异常痛苦,虽然可以通过 Ansible、Puppet 等部署工具进行交付,但是也很难保证对底层各种异构的环境支持得很好,还有随时会出现的版本漂移问题。比如你可能经常遇到的,某个软件包几个月之前安装还能够正常运行,现在到一个新环境安装后,竟然无法正常工作了。

不可变基础设施则是另一种思路,部署完成以后,便成为一种只读状态,不可对其进行任何更改。如果需要更新或修改,就使用新的环境或服务器去替代旧的。不可变基础设施带来了更一致、更可靠、更可预测的设计理念,可以缓解或完全避免可变基础设施中遇到的各种常见问题。

同时,借助容器技术我们可以自动化地构建出不可变的、可版本化管理的、可一致性交付的应用服务体系,这里包括了标准化实例、运行环境等。还可以依赖持续部署系统,进行应用服务的自动化部署更新,加快迭代和部署效率。

Kubernetes 中的不可变基础设施就是 Pod。

Pod 是个啥?​

Pod 由一个或多个容器组成,Pod 中的容器不可分割,会作为一个整体运行在一个 Node 节点上,也就是说 Pod 是你在 Kubernetes 中可以创建和部署的最原子化的单位。

如下不能单独列出容器,但是可以列出 Pod

$ kubectl get pods -A
NAME READY STATUS RESTARTS AGE
kubia 1/1 Running 0 30m
nginx-7cbb8cd5d8-2jgcp 1/1 Running 1 (42m ago) 4d2h
备注

同一个 Pod 中的容器共享网络、存储资源。

  • 每个 Pod 都会拥有一个独立的网络空间,其内部的所有容器都共享网络资源,即 IP 地址、端口。内部的容器直接通过 localhost 就可以通信。
  • Pod 可以挂载多个共享的存储卷(Volume),这时内部的各个容器就可以访问共享的 Volume 进行数据的读写。

每个 pod 就像一个独立的逻辑机器,拥有自己的 IP、主机名、进程等,运行一个独立的应用程序。(注意这个 IP 是集群内部的,不能从集群外部访问)

这个运行的应用程序可以是单个进程,运行在单个容器中,也可以是一个主应用进程加其他支持进程,每个进程都在自己的容器中运行。

为什么要允许一个 Pod 内可以包含多个容器?​

再回答这个问题之前,我们思考一下另外一个问题 “为什么不直接在单个容器里运行多个程序?”。

由于容器实际上是一个 “单进程” 的模型,这点非常重要。因为如果你在容器里启动多个进程,这将会带来很多麻烦。不仅它们的日志记录会混在一起,它们各自的生命周期也无法管理。毕竟只有一个进程的 PID 可以为 1,如果 PID 为 1 的进程这个时候挂了,或者说失败退出了,那么其他几个进程就会自然而然地成为 “孤儿”,无法管理,也无法回收资源。

很多公司在刚开始容器化改造的时候,都会这么去使用容器,把容器当作 VM 来使用,有时候也叫作富容器模式。这其实是一种非常不好的尝试,也不符合不可变基础设施的理念。我们可以接受将富容器当作容器化改造的一个短暂的过渡形态,但不能将其作为改造的终态。后续,还需要进一步对这些富容器进行拆分、解耦。

看到这里,第二个问题的答案已经呼之欲出了。用一个 Pod 管理多个容器,既能够保持容器之间的隔离性,还能保证相关容器的环境一致性。使用粒度更小的容器,不仅可以使应用间的依赖解耦,还便于使用不同技术栈进行开发,同时还可以方便各个开发团队复用,减少重复造轮子。

Pod 里面的容器组合原则​

既然一个 Pod 内支持定义多个容器,是不是意味着我可以任意组合,甚至将无关紧要的容器放进来都无所谓?不!这不是我们推荐的方式,也不是使用 Pod 的正确打开方式。

通常来说,如果在一个 Pod 内有多个容器,那么这几个容器最好是密切相关的,且可以共享一些资源的,比如网络、存储等。

来看看 官方文档中给的一个例子。这个 Pod 里面运行了两个容器 File Puller 和 Web Server。其中 File Puller 负责定期地从外部 Content Manager 同步内容,更新到挂载的共享存储卷(Volume)中,而 Web Server 只负责对外提供访问服务。两个容器之间通过共享的存储卷共享数据。

例如,你可能有一个容器,为共享卷中的文件提供 Web 服务器支持,以及一个单独的 "边车 (sidercar)" 容器负责从远端更新这些文件,如下图所示:

有些 Pod 具有 Init 容器和 应用容器。 Init 容器会在启动应用容器之前运行并完成。

Pod 天生地为其成员容器提供了两种共享资源:网络和存储。

提示

值得注意的是,当一个 pod 包含多个容器时,这些容器总是运行于同一个工作节点上,一个 Pod 绝不会跨越多个工作节点

类似这样紧密耦合的业务容器,就比较适合放置在同一个 Pod 中,可以保证很高的通信效率。

一般来说,在一个 Pod 内运行多个容器,比较适应于以下这些场景。

1、容器之间会发生文件交换等,上面提到的例子就是这样。一个写文件,一个读文件。

2、容器之间需要本地通信,比如通过 localhost 或者本地的 Socket。这种方式有时候可以简化业务的逻辑,因为此时业务就不用关心另外一个服务的地址,直接本地访问就可以了。

3、容器之间需要发生频繁的 RPC 调用,出于性能的考量,将它们放在一个 Pod 内。

4、希望为应用添加其他功能,比如日志收集、监控数据采集、配置中心、路由及熔断等功能。这时候可以考虑利用边车模式(Sidecar Pattern),既不需要改动原始服务本身的逻辑,还能增加一系列的功能。比如 Fluentd 就是利用边车模式注入一个对应 log agent 到 Pod 内,用于日志的收集和转发。 Istio 也是通过在 Pod 内放置一个 Sidecar 容器,来进行无侵入的服务治理。

如何声明一个 Pod​

在 Kubernetes 中,所有对象都可以通过一个相似的 API 模板来描述,它们主要包含5个部分:

  • apiVersion:版本,有 kubernetes 内部定义,版本号必须用 kubectl api-versions 查询。
  • kind:类型,有 kubernetes 内部定义,类型必须用 kubectl api-resources 查询。
  • metadata:元数据,主要是资源标识和说明,常用的有 name、namespace、labels 等。
  • spec:描述,这是配置中最重要的一部分,里面是对各种资源配置的详细描述。
  • status:状态信息,里面的内容不需要定义,由 kubernetes 自动生成。

Kubernetes 有了这种统一风格的 API 定义,方便了通过 REST 接口进行开发和管理。

可以通过 explain 查看具体的配置项

# 查看某种资源可以配置的一级配置
kubectl explain 资源类型
# 查看属性的子属性
kubectl explain 资源类型.属性

例如查看资源类型为 pod 的可配置项

Pod 的类型 Kind​

在 Kubernetes 中,Pod 是最小的部署单元,用于承载容器。以下是一些常见的 Pod 的 kind:

  • Pod:最基本的 Pod,它是由一个或多个紧密关联的容器组成的。
  • ReplicaSet:用于管理多个副本的 Pod,并确保指定数量的 Pod 始终运行。
  • Deployment:建立在 ReplicaSet 之上的控制器,用于管理应用程序的部署和更新。
  • StatefulSet:用于管理有状态应用程序的 Pod,并确保这些 Pod 按照一定的顺序启动和关闭。
  • DaemonSet:用于在集群的每个节点上运行一个副本的 Pod,通常用于运行系统级别的服务或网络代理。
  • Job:用于一次性任务的 Pod,当任务完成后自动终止。
  • CronJob:用于按照计划执行任务的 Pod。

元数据(metadata)​

metadata 中一般要包含如下 3 个对该对象至关重要的元信息:namespace(命名空间)、name(对象名)和 uid(对象 ID)。

1、namespace 是 Kubernetes 中比较重要的一个概念,是对一组资源和对象的抽象集合,namespace 主要用于逻辑上的隔离。

$ kubectl get ns

Kubernetes 中有几个内置的 namespace:

  • default,这是默认的缺省命名空间;
  • kube-system,主要是部署集群最关键的核心组件,比如一般会将 CoreDNS 声明在这个 namespace 中;
  • kube-public,是由 kubeadm 创建出来的,主要是保存一些集群 bootstrap 的信息,比如 token 等;
  • kube-node-lease,它要用于 node 汇报心跳,每一个节点都会有一个对应的 Lease 对象。

2、uid 是由系统自动生成的,主要用于 Kubernetes 内部标识使用,比如某个对象经历了删除重建,单纯通过名字是无法判断该对象的新旧,这个时候就可以通过 uid 来进行唯一确定。

当然, Kubernetes 中并不是所有对象都是 namespace 级别的,还有一些对象是集群级别的,并不需要 namespace 进行隔离,比如 Node 资源等。

除此以外,还可以在 metadata 里面用各种标签 (labels)和注释(annotations)来标识和匹配不同的对象,比如用户可以用标签env=dev来标识开发环境,用env=testing来标识测试环境。

规范 (Spec)​

在 Spec 中描述了该对象的详细配置信息,即用户希望的状态(Desired State)。Kubernetes 中的各大组件会根据这个配置进行一系列的操作,将这种定义从“抽象”变为“现实”,我们称之为调和(Reconcile)。用户不需要过度关心怎么达到终态,也不用参与。

继续看下它的常见子属性:

containers <[]Object>:容器列表,用于定义容器的详细信息。
nodeName <String>:根据nodeName的值将Pod调度到指定的Node节点上。
nodeSelector <map[]> :根据NodeSelector中定义的信息选择该Pod调度到包含这些Label的Node上。
hostNetwork <boolean>:是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络。
volumes <[]Object> :存储卷,用于定义Pod上面挂载的存储信息。
restartPolicy <string>:重启策略,表示Pod在遇到故障的时候的处理策略。

整个结构如下图所示

状态(Status)​

在这个字段里面,包含了该对象的一些状态信息,会由各个控制器定期进行更新。也是不同控制器之间进行相互通信的一个渠道。

在 Kubernetes 中,各个组件都是分布式部署的,围绕着 kube-apiserver 进行通信,那么不同组件之间进行信息同步,就可以通过 status 进行。像 Node 的 status 就记录了该节点的一些状态信息,其他的控制器,就可以通过 status 知道该 Node 的情况,做一些操作,比如节点宕机修复、可分配资源等。

通过 kubectl 创建 Pod 成功后(下面那节),可以通过如下命令看到 Pod 的状态:

# 这里使用了 kubectl 命令行 JSONPATH 模板能力
$ kubectl get pod twocontainers -o=jsonpath='{.status.phase}'
Running

在整个生命周期中,Pod 会出现 5 种状态(相位),分别如下:

  • 挂起(Pending):apiserver 已经创建了 Pod 资源对象,但它尚未被调度完成或者仍处于下载镜像的过程中
  • 运行中(Running):Pod 已经被调度至某节点,并且所有容器都已经被 kubelet 创建完成
  • 成功(Succeeded):Pod 中的所有容器都已经成功终止并且不会被重启
  • 失败(Failed):所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态
  • 未知(Unknown):apiserver 无法正常获取到 Pod 对象的状态信息,通常由网络通信失败所导致

查看 Pod 信息​

查询所有 Pod 的基本信息

kubectl get pods [-n 命名空间的名称]

查询名称为 dev 的 namespace 下的所有 Pod 的基本信息

kubectl get pods -n dev

查看 Pod 的详细信息

# kubectl describe pod nginx -n dev
kubectl describe pod <pod的名称> [-n 命名空间名称]

Pod 的访问

# 获取 Pod 的IP
kubectl get pods [-n dev] -o wide


# 通过curl访问
curl ip:端口

例如访问 Nginx 的 Pod

简单的 Pod 例子​

创建一个 twocontainers.yaml 文件

apiVersion: v1 # 指定当前描述文件遵循v1版本的Kubernetes API
kind: Pod # 我们在描述一个 pod
metadata:
name: twocontainers # 指定 pod 的名称
namespace: default # 指定当前描述的 pod 所在的命名空间
labels: # 指定 pod 标签
app: twocontainers
annotations: # 指定 pod 注释
version: v0.5.0
releasedBy: david
purpose: demo
spec:
containers:
- name: simplesrv # 容器的名称
image: quay.io/openshiftlabs/simpleservice:0.5.0 # 创建容器所使用的镜像
ports:
- containerPort: 9876 # 应用监听的端口
- name: simplesrv-shell # 容器的名称
image: ubuntu:latest # 创建容器所使用的镜像
command: # 容器启动命令
- 'bin/bash'
- '-c'
- 'sleep 10000'

创建 Pod

$ kubectl create -f ./twocontainers.yaml
$ kubectl get pods

创建出来后,稍微等待一下,我们就可以看到,该 Pod 已经运行成功了。现在我们可以通过 exec 进入 simplesrv-shell 这个容器,来访问 simplesrv 服务:

# kubectl exec pod名称 -n 命名空间 -it -c 容器名称 /bin/sh
$ kubectl exec twocontainers -c simplesrv-shell -it /bin/sh

可以发现已经能访问到 simplesrv 服务了,这就是一个简单的 Pod 的例子。

$ curl -s localhost:9876/info

检查已有的 Pod 描述文件​

可以通过 get 命令来检查已有的 Pod 描述文件

# -w 参数可以实时查看 Pod 的状态
$ kubectl get po nginx -o yaml -w

相对完整的描述文件介绍​

下面是 Pod 的资源清单:

apiVersion: v1 # 必选,版本号,例如v1
kind: Pod # 必选,资源类型,例如 Pod
metadata: # 必选,元数据
name: string # 必选,Pod名称
namespace: string # Pod所属的命名空间,默认为 "default"
labels: # 自定义标签列表
- name: string
spec: # 必选,Pod中容器的详细定义
containers: # 必选,Pod中容器列表
- name: string # 必选,容器名称
image: string # 必选,容器的镜像名称
imagePullPolicy: [Always|Never|IfNotPresent] # 获取镜像的策略
command: # 容器的启动命令列表,如不指定,使用打包时使用的启动命令
- string
args: # 容器的启动命令参数列表
- string
workingDir: string # 容器的工作目录
volumeMounts: # 挂载到容器内部的存储卷配置
- name: string # 引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
mountPath: string # 存储卷在容器内 mount 的绝对路径,应少于512字符
readOnly: boolean # 是否为只读模式
ports: # 需要暴露的端口库号列表
- name: string # 端口的名称
containerPort: int # 容器需要监听的端口号
hostPort: int # 容器所在主机需要监听的端口号,默认与Container相同
protocol: string # 端口协议,支持TCP和UDP,默认TCP
env: # 容器运行前需设置的环境变量列表
- name: string # 环境变量名称
value: string # 环境变量的值
resources: # 资源限制和请求的设置
limits: # 资源限制的设置
cpu: string # Cpu的限制,单位为 core 数,将用于docker run --cpu-shares参数
memory: string # 内存限制,单位可以为 Mib/Gib,将用于docker run --memory参数
requests: # 资源请求的设置
cpu: string # Cpu请求,容器启动的初始可用数量
memory: string # 内存请求,容器启动的初始可用数量
lifecycle: # 生命周期钩子
postStart: null # 容器启动后立即执行此钩子,如果执行失败,会根据重启策略进行重启
preStop: null # 容器终止前执行此钩子,无论结果如何,容器都会终止
livenessProbe: # 对Pod内各容器健康检查的设置,当探测无响应几次后将自动重启该容器
exec: # 对Pod容器内检查方式设置为exec方式
command: # exec 方式需要制定的命令或脚本
- string
httpGet: # 对Pod内个容器健康检查方法设置为 HttpGet,需要制定 Path、port
path: string
port: number
host: string
scheme: string
HttpHeaders:
- name: string
value: string
tcpSocket: # 对Pod内个容器健康检查方式设置为tcpSocket方式
port: number
initialDelaySeconds: 0 # 容器启动完成后首次探测的时间,单位为秒
timeoutSeconds: 0 # 对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
periodSeconds: 0 # 对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
successThreshold: 0
failureThreshold: 0
securityContext:
privileged: false
restartPolicy: [Always | Never | OnFailure] # Pod的重启策略
nodeName: <string> # 设置 NodeName 表示将该Pod调度到指定到名称的node节点上
nodeSelector: object # 设置NodeSelector表示将该Pod调度到包含这个label的node上
imagePullSecrets: # Pull 镜像时使用的 secret 名称,以 key:secretkey 格式指定
- name: string
# 是否使用主机网络模式,默认为 false,如果设置为 true,表示使用宿主机网络
hostNetwork: false
# 在该pod上定义共享存储卷列表
volumes: # 共享存储卷名称 (volumes 类型有很多种)
- name: string
emptyDir: {} # 类型为 emptyDir 的存储卷,与Pod同生命周期的一个临时目录。为空值
hostPath: string # 类型为 hostPath 的存储卷,表示挂载 Pod 所在宿主机的目录
path: string # Pod 所在宿主机的目录,将被用于同期中 mount 的目录
secret: # 类型为secret的存储卷,挂载集群与定义的secret对象到容器内部
scretname: string
items:
- key: string
path: string
configMap: # 类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
name: string
items:
- key: string
path: string

Pod 的补充知识​

共享 IPC 命名空间​

容器中进程交互还是采用了 Linux 常见的进程间交互方法 IPC(Inter-Process Communication,进程间通信),包括信号量、消息队列和共享内存等。

然而同 VM 不同的是,容器的进程间交互实际上还是 host 上具有相同 pid 命名空间中的进程间交互,因此需要在 IPC 资源申请时加入命名空间信息,每个 IPC 资源有一个唯一的 32 位 id。

提示

不同用户的进程就是通过 pid 命名空间隔离开的,且不同命名空间中可以有相同 pid。所有的 LXC 进程在 Docker 中的父进程为 Docker 进程,每个 LXC 进程具有不同的命名空间。同时由于允许嵌套,因此可以很方便的实现嵌套的 Docker 容器。

共享 Network 命名空间​

由于一个 pod 中的容器运行于相同的 Network 命名空间中,因此它们共享相同的 IP 地址和端口空间。 这意味着在同一 pod 中的容器运行的多个进程需要注意不能绑定到相同的端口号,否则会导致端口冲突,但这只涉及同一 pod 中的容器。

由于每个 pod 都有独立的端口空间,对于不同 pod 中的容器来说则永远不会遇到端口冲突。此外, 一个 pod 中的所有容器也都具有相同的 loopback 网络接口,因此容器可以通过 localhost 与同一 pod 中的其他容器进行通信。

Pod 的具体使用​

查看 pod.spec.containers 的可选配置项

kubectl explain pod.spec.containers
# 返回的重要属性
KIND:     Pod
VERSION:  v1
RESOURCE: containers <[]Object>   # 数组,代表可以有多个容器FIELDS:
name  <string>     # 容器名称
image <string>     # 容器需要的镜像地址
imagePullPolicy  <string> # 镜像拉取策略
command  <[]string> # 容器的启动命令列表,如不指定,使用打包时使用的启动命令
args   <[]string> # 容器的启动命令需要的参数列表
env   <[]Object> # 容器环境变量的配置
ports <[]Object> # 容器需要暴露的端口号列表
resources <Object> # 资源限制和资源请求的设置

创建一个基本的 Pod​

创建 pod-base.yaml 文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
name: pod-base
namespace: dev
labels:
user: alsritter
spec:
containers:
- name: nginx # 容器名称
image: nginx:1.17.1 # 容器需要的镜像地址
- name: busybox # 容器名称
image: busybox:1.30 # 容器需要的镜像地址

上面定义了一个比较简单的 Pod 的配置,里面有两个容器:

  • nginx:用的是1.17.1版本的nginx镜像创建
  • busybox:用的是1.30版本的busybox镜像创建

创建 Pod:

kubectl apply -f pod-base.yaml

查看 Pod 状况:

kubectl get pod -n dev

通过 describe 查看内部的详情:

# 此时已经运行起来了一个基本的Pod,虽然它暂时有问题
kubectl describe pod pod-base -n dev

或者使用 create 与 delete 执行创建和删除命令:

kubectl create -f pod-nginx.yaml

kubectl delete -f pod-nginx.yaml

检查 Pod 的状态​

查看 pod 状态:可以使用 kubectl get pods 命令查看当前集群中所有的 pod 以及它们的状态,例如:

kubectl get pods

# 输出:
NAME READY STATUS RESTARTS AGE
nginx-6f76d4d9c5-2sb8j 1/1 Running 0 30s
nginx-6f76d4d9c5-7krqn 1/1 Running 0 30s

1、查看 pod 日志:可以使用 kubectl logs 命令查看 pod 日志,例如:

kubectl logs nginx-6f76d4d9c5-2sb8j

# 输出:
nginx version: nginx/1.19.9
...

如果你想实时监视 pod 日志的输出,可以使用 kubectl logs -f 命令,例如:kubectl logs -f nginx-6f76d4d9c5-2sb8j。

2、进入 pod 内部:可以使用 kubectl exec 命令进入 pod 内部进行操作,例如:

kubectl exec -it nginx-6f76d4d9c5-2sb8j /bin/bash

# 输出:
root@nginx-6f76d4d9c5-2sb8j:/#

上面的命令会打开一个 shell 终端,并进入到 nginx-6f76d4d9c5-2sb8j 这个 pod 的容器内部。你可以在这个终端里面执行命令来进行调试和操作。

3、查看 pod 的详细信息:可以使用 kubectl describe 命令查看 pod 的详细信息,例如:

kubectl describe pod <Pod名称> -n 命名空间

查看具体的容器日志​

# 想查看 Pods 下面的容器
kubectl get pods -n dev
kubectl describe pod <Pod名称> -n 命名空间
kubectl logs <pod_name> -c <container_name>

# 假如当前pod只有一个容器,运行以下命令即可
kubectl exec -it nginx-56b8c64cb4-t97vb -- /bin/bash

通过命令来创建删除 Pod​

kubernetes 没有提供单独运行 Pod 的命令,都是通过 Pod 控制器来实现的

# --image      指定 Pod 的镜像
# --port 指定端口
# --namespace 指定 namespace
kubectl run nginx --image=nginx:latest --port=80 --namespace dev

删除指定的 Pod(花费的时间有点久)

# kubectl delete pod nginx -n dev
kubectl delete pod <pod的名称> [-n 命名空间]

# 检查是否被删除了
kubectl get pods --all-namespaces --no-headers --watch-only

镜像的拉取策略​

创建 pod-imagepullpolicy.yaml 文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
name: pod-imagepullpolicy
namespace: dev
labels:
user: alsritter
spec:
containers:
- name: nginx # 容器名称
image: nginx:1.17.1 # 容器需要的镜像地址
imagePullPolicy: Always # 用于设置镜像的拉取策略
- name: busybox # 容器名称
image: busybox:1.30 # 容器需要的镜像地址

这个 imagePullPolicy 用于设置镜像拉取的策略,kubernetes 支持配置三种拉取策略:

  • Always:总是从远程仓库拉取镜像(一直远程下载)。
  • IfNotPresent:本地有则使用本地镜像,本地没有则从远程仓库拉取镜像(本地有就用本地,本地没有就使用远程下载)。
  • Never:只使用本地镜像,从不去远程仓库拉取,本地没有就报错(一直使用本地,没有就报错)。

默认值说明:

  • 如果镜像 tag 为具体的版本号,默认策略是 IfNotPresent。
  • 如果镜像 tag 为latest(最终版本),默认策略是 Always。

创建 Pod:

kubectl apply -f pod-imagepullpolicy.yaml

通过 describe 查看内部的详情:

kubectl describe pod pod-imagepullpolicy -n dev

output:

Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully assigned dev/pod-imagePullPolicy to node1
Normal Pulling 32s kubelet, node1 Pulling image "nginx:1.17.1"
Normal Pulled 26s kubelet, node1 Successfully pulled image "nginx:1.17.1"
Normal Created 26s kubelet, node1 Created container nginx
Normal Started 25s kubelet, node1 Started container nginx
Normal Pulled 7s (x3 over 25s) kubelet, node1 Container image "busybox:1.30" already present on machine
Normal Created 7s (x3 over 25s) kubelet, node1 Created container busybox
Normal Started 7s (x3 over 25s) kubelet, node1 Started container busybox

通过执行定时任务来保活​

在前面的案例中,一直有一个问题没有解决,就是 busybox 容器一直没有成功运行,那么到底是什么原因导致这个容器的故障呢?

原来 busybox 并不是一个程序,而是类似于一个工具类的集合,kubernetes 集群启动管理后,它会自动关闭。

所以解决方法就是让其一直在运行,这就用到了 command 配置。

创建 pod-command.yaml 文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
name: pod-command
namespace: dev
labels:
user: alsritter
spec:
containers:
- name: nginx # 容器名称
image: nginx:1.17.1 # 容器需要的镜像地址
imagePullPolicy: IfNotPresent # 设置镜像拉取策略
- name: busybox # 容器名称
image: busybox:1.30 # 容器需要的镜像地址
command: ["/bin/sh","-c","touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt;sleep 3;done;"]

command:用于在 Pod 中的容器初始化完毕之后执行一个命令

# 上面的这句命令表示每隔3秒,向文件写入当前时间
while true; do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done
# 创建Pod
$ kubectl create -f pod-command.yaml

# 查看Pod状态
# 此时发现两个pod都正常运行了
$ kubectl get pods pod-command -n dev
NAME READY STATUS RESTARTS AGE
pod-command 2/2 Runing 0 2s

进入 pod 中的 busybox 容器,查看文件内容,这里补充一个命令:

# 进入容器
kubectl exec pod名称 -n 命名空间 -it -c 容器名称 /bin/sh

使用这个命令就可以进入某个容器的内部,然后进行相关操作了,比如,可以查看 txt 文件的内容

$ kubectl exec pod-command -n dev -it -c busybox /bin/sh 
>
> tail -f /tmp/hello.txt
14:44:19
14:44:22
14:44:25

特别说明:通过上面发现 command 已经可以完成启动命令和传递参数的功能,为什么这里还要提供一个 args 选项,用于传递参数呢?

这其实跟 docker 有点关系,kubernetes 中的 command、args 两项其实是实现覆盖 Dockerfile 中 ENTRYPOINT 的功能。

  1. 如果 command 和 args 均没有写,那么用 Dockerfile 的配置。
  2. 如果 command 写了,但 args 没有写,那么 Dockerfile 默认的配置会被忽略,执行输入的 command
  3. 如果 command 没写,但 args 写了,那么 Dockerfile 中配置的 ENTRYPOINT 的命令会被执行,使用当前 args 的参数
  4. 如果 command 和 args 都写了,那么 Dockerfile 的配置被忽略,执行 command 并追加上 args 参数

容器的端口设置​

本小节来介绍容器的端口设置,也就是 containers 的 ports 选项。

$ kubectl explain pod.spec.containers.ports
KIND: Pod
VERSION: v1
RESOURCE: ports <[]Object>
FIELDS:
name <string> # 端口名称,如果指定,必须保证name在pod中是唯一的
containerPort<integer> # 容器要监听的端口(0<x<65536)
hostPort <integer> # 容器要在主机上公开的端口,如果设置,主机上只能运行容器的一个副本(一般省略)
hostIP <string> # 要将外部端口绑定到的主机IP(一般省略)
protocol <string> # 端口协议。必须是UDP、TCP或SCTP。默认为“TCP”。

接下来,编写一个测试案例,创建 pod-ports.yaml

apiVersion: v1
kind: Pod
metadata:
name: pod-ports
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports: # 设置容器暴露的端口列表
- name: nginx-port
containerPort: 80
protocol: TCP

此时访问容器中的程序使用的是

Podip:containerPort

资源配额​

容器中的程序要运行,肯定是要占用一定资源的,比如 CPU 和内存等,如果不对某个容器的资源做限制,那么它就可能吃掉大量资源,导致其它容器无法运行。针对这种情况,kubernetes 提供了对内存和 CPU 的资源进行配额的机制,这种机制主要通过 resources 选项实现,他有两个子选项:

  • limits:用于限制运行时容器的最大占用资源,当容器占用资源超过 limits 时会被终止,并进行重启
  • requests :用于设置容器需要的最小资源,如果环境资源不够,容器将无法启动

可以通过上面两个选项设置资源的上下限。

接下来,编写一个测试案例,创建 pod-resources.yaml

apiVersion: v1
kind: Pod
metadata:
name: pod-resources
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
resources: # 资源配额
limits: # 限制资源(上限)
cpu: "2" # CPU限制,单位是core数
memory: "10Gi" # 内存限制
requests: # 请求资源(下限)
cpu: "1" # CPU限制,单位是core数
memory: "10Mi" # 内存限制

在这对 cpu 和 memory 的单位做一个说明:

  • cpu:core 数,可以为整数或小数
  • memory:内存大小,可以使用Gi、Mi、G、M等形式

在外部让容器执行命令​

kubectl exec kubia-7nog1 -- curl -s http://baidu.com

这个 kubia-7nog1 表示具体的 Pod 容器,后面跟随的这个 -- 后面的内容表示 Pod 内部需要执行的命令,如果不加 --,那么 kubectl 可能会把 -s 参数解析成 kubectl 自己的 -s 选项

钩子函数​

钩子函数能够感知自身生命周期中的事件,并在相应的时刻到来时运行用户指定的程序代码。

重新拉取 kubernetes Pods 镜像​

kubectl get pod PODNAME -n NAMESPACE -o yaml | kubectl replace --force -f -

References​